#include "network_interface.hh"


#include <iostream>

// Dummy implementation of a network interface
// Translates from {IP datagram, next hop address} to link-layer frame, and from link-layer frame to IP datagram

// For Lab 5, please replace with a real implementation that passes the
// automated checks run by `make check_lab5`.

// You will need to add private members to the class declaration in `network_interface.hh`

template<typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using namespace std;

//! \param[in] ethernet_address Ethernet (what ARP calls "hardware") address of the interface
//! \param[in] ip_address IP (what ARP calls "protocol") address of the interface
NetworkInterface::NetworkInterface(const EthernetAddress &ethernet_address, const Address &ip_address)
        : _ethernet_address(ethernet_address), _ip_address(ip_address) {
    cerr << "DEBUG: Network interface has Ethernet address " << to_string(_ethernet_address) << " and IP address "
         << ip_address.ip() << "\n";
}

//! \param[in] dgram the IPv4 datagram to be sent
//! \param[in] next_hop the IP address of the interface to send it to (typically a router or default gateway, but may also be another host if directly connected to the same network as the destination)
//! (Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) with the Address::ipv4_numeric() method.)
void NetworkInterface::send_datagram(const InternetDatagram &dgram, const Address &next_hop) {
    // convert IP address of next hop to raw 32-bit representation (used in ARP header)
    const uint32_t next_hop_ip = next_hop.ipv4_numeric();
    EthernetFrame ef;
    ef.header().type = EthernetHeader::TYPE_IPv4;
    ef.header().src = _ethernet_address;
    ef.payload() = dgram.serialize();
    if (_cache.find(next_hop_ip) != _cache.end()) {
        ef.header().dst = _cache[next_hop_ip];
        _frames_out.push(move(ef));
    } else {
        _broadcast_frames[next_hop_ip].push_back(move(ef));
        bool have_sent_to_ip = _all_unlearned.find(next_hop_ip) != _all_unlearned.end();
        if (not have_sent_to_ip) {
            _unlearned_ips[_ticks].insert(next_hop_ip);
            _all_unlearned.insert(next_hop_ip);
            ARPMessage &&request = gen_arp_msg(ARPMessage::OPCODE_REQUEST,
                                               {0, 0, 0, 0, 0, 0},
                                               next_hop_ip);
            send_an_arp(ETHERNET_BROADCAST, request.serialize());
        }
    }

}

//! \param[in] frame the incoming Ethernet frame
optional<InternetDatagram> NetworkInterface::recv_frame(const EthernetFrame &frame) {
    const auto &header = frame.header();
    if (header.dst != _ethernet_address && header.dst != ETHERNET_BROADCAST)
        return nullopt;
    if (header.type == EthernetHeader::TYPE_IPv4) {
        InternetDatagram dgram;
        if (dgram.parse(frame.payload()) == ParseResult::NoError) return dgram;
        return nullopt;
    } else if (header.type == EthernetHeader::TYPE_ARP) {
        ARPMessage msg;
        if (msg.parse(frame.payload()) != ParseResult::NoError)return nullopt;
        uint32_t ip = msg.sender_ip_address;
        _cache[ip] = msg.sender_ethernet_address;
        _time_of_arp[_ticks].insert(ip);
        if (msg.opcode == ARPMessage::OPCODE_REQUEST &&
            msg.target_ip_address == _ip_address.ipv4_numeric()) {
            ARPMessage &&reply = gen_arp_msg(ARPMessage::OPCODE_REPLY,
                                             msg.sender_ethernet_address,
                                             msg.sender_ip_address);
            send_an_arp(msg.sender_ethernet_address, reply.serialize());
        }
        if (_broadcast_frames.find(ip) != _broadcast_frames.end()) {
            for (auto &bframe:_broadcast_frames[ip]) {
                bframe.header().dst = msg.sender_ethernet_address;
                _frames_out.push(move(bframe));
            }
            _broadcast_frames.erase(ip);
        }
    }
    return nullopt;
}

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void NetworkInterface::tick(const size_t ms_since_last_tick) {
    _ticks += ms_since_last_tick;

    auto it1 = _unlearned_ips.lower_bound(_ticks - 4999);
    for (auto it = _unlearned_ips.begin(); it != it1; ++it)
        for (const auto &ip : it->second)
            _all_unlearned.erase(ip);
    _unlearned_ips.erase(_unlearned_ips.begin(), it1);

    auto it2 = _time_of_arp.lower_bound(_ticks - 29999);
    for (auto it = _time_of_arp.begin(); it != it2; ++it)
        for (const auto &ip : it->second)
            _cache.erase(ip);
    _time_of_arp.erase(_time_of_arp.begin(), it2);

}

void NetworkInterface::send_an_arp(const EthernetAddress &dst, const BufferList &payload) {
    EthernetFrame arp_frame;
    arp_frame.header().type = EthernetHeader::TYPE_ARP;
    arp_frame.header().src = _ethernet_address;
    arp_frame.header().dst = dst;
    arp_frame.payload() = payload;
    _frames_out.push(move(arp_frame));
}

ARPMessage NetworkInterface::gen_arp_msg(uint16_t opcode, EthernetAddress dst, uint32_t dst_ip) {
    ARPMessage arp_msg;
    arp_msg.opcode = opcode;
    arp_msg.sender_ethernet_address = _ethernet_address;
    arp_msg.sender_ip_address = _ip_address.ipv4_numeric();
    arp_msg.target_ethernet_address = dst;
    arp_msg.target_ip_address = dst_ip;
    return arp_msg;
}
